Conversation
cbb31e5 to
ad71f13
Compare
|
Making Worker channel configuration available to team-scoped L2 users is valuable and should not require admin or host access for every normal change. However, channel metadata and channel credentials must have different permission boundaries. By default, L2 users should be able to view and edit non-sensitive fields for Workers in their own teams, such as channel type, endpoint, enabled state, display settings, health, and restart operations. Sensitive values such as Even with FullAccess, the safer default API contract is:
Please also use dedicated channel read, channel update, and sensitive-data actions instead of inheriting the generic Worker Before moving this PR out of Draft, please open a GitHub Issue describing the role/capability matrix, sensitive-field classification, secret update semantics, QR credential handling, and audit requirements, then link it to this PR for community discussion. The Issue should also confirm the QwenPaw version contract: the locked QwenPaw 2.0.1 package does not currently expose the proxied 让本团队 L2 用户配置 Worker Channel 具有明确价值,普通配置变更不应该每次都依赖 Admin 或宿主机操作。但是,Channel 元数据和 Channel 凭据必须采用不同的权限边界。 默认 L2 应能查看和编辑本团队 Worker 的非敏感字段,例如 Channel 类型、地址、启用状态、展示配置、健康状态和重启操作。
即使具有 FullAccess,更安全的默认接口契约仍应是:
请为 Channel 读取、Channel 修改和敏感数据访问定义独立 Action,不要继承通用 Worker 在将本 PR 移出 Draft 前,请创建 GitHub Issue,说明角色/capability 矩阵、敏感字段分类、Secret 更新语义、二维码凭据处理和审计要求,并关联到本 PR,让社区参与讨论。 Issue 中还应确认 QwenPaw 版本契约:当前锁定的 QwenPaw 2.0.1 没有被代理的 |
ad71f13 to
9abdb96
Compare
2c8174c to
3c9ccdd
Compare
|
Thanks for the channel proxy work. Separately from the permission design, there is a functional contract issue that blocks this PR. The current QwenPaw worker client and integration coverage use /api/config/channels..., but this proxy forwards every channel request to /config/channels... without the /api prefix. Those calls will miss the Worker API router at runtime. In addition, the currently pinned QwenPaw version does not provide the proxied conflict-check route. Please update the forwarding paths to the actual /api/config/channels... contract, and either remove or version-gate conflict-check, or make the required QwenPaw upgrade an explicit prerequisite. Please add a version-contract or integration test that exercises the real prefixed route. |
3c9ccdd to
357324c
Compare
|
Thanks — you're right on all three counts, and the first revision genuinely would have 404'd at runtime. Fixed in
Also fixed while in there: the PR body's stale "draft / #1212 pending" framing — #1212 is merged to main, so the L2 write path is functional as described.
Local gates on 感谢——三点都成立,首版在运行时确实会 404。已在
顺手修正:PR body 里过时的「draft / 等 #1212」表述——#1212 已合入 main,L2 写路径按描述已可用。
|
Proxy a fixed surface of each worker's qwenpaw app channel API (list/types/schemas/get/put/health/restart/conflict-check/qrcode) so L1 admins and L2 humans can connect channels (QQ/Matrix/DingTalk/...) to agents in their teams from a graphical frontend, without SSH or container surgery on the worker's agent.json. - Embedded mode only (kube: uniform 503, before any worker lookup) - W8 scope: cross-team and standalone-worker access hides as 404; the handler is the real boundary (team leaders read-only, 403 on mutations) on top of the middleware's worker-scoped policy - PUT is the qwenpaw-authoritative write path (persist + hot reload, no worker restart); empty body rejected pre-dial (would wipe the channel); upstream 400/404/409/422 pass through verbatim - Read-back validation: after a 200, verify the worker's push_loop converged the MinIO baseline and report it via the X-AgentTeams-MinIO-Persisted header (true/false/skipped) — surfaces the manual-edit persistence gaps that burned channel secrets before - Fixed-path forwarding only (never a generic reverse proxy), strict channel-name charset, strict qrcode/status query whitelist, per-mutation audit log - 20 handler tests (forwarding, read-back branches, W8 404, leader 403, kube 503, upstream status mapping, query whitelist) - Contract doc (docs/design/worker-channels-api.md) + usage section (EN/zh-cn) under Worker channel configuration Note: L2 write authorization at the middleware rides on the worker-scoped update policy (separate PR); until it lands, L2 PUTs are denied by the middleware and only L1 reaches the handler.
…minimum Verified byte-identical across official PyPI wheels: QwenPaw 2.0.1 / 2.2.0 / 2.2.1 all expose the same 9 forwarded routes under /api. conflict-check is an additive 2.2.x-only route (absent in 2.0.x), proxied as a small follow-up after a 2.2.x pin. No code-logic change; proxy stays pure forwarding with 404 pass-through as version gate.
e185060 to
4216a80
Compare
Worker channel configuration proxy (GET/PUT /api/v1/workers/{name}/channels...)
Summary
Connecting a worker to a messaging channel (QQ / Matrix / DingTalk / Feishu / WeChat / ...) has always required host access: SSH in,
docker exec, hand-editchannelsin the worker'sagent.json, watch for the hot-reload log line. The worker's qwenpaw app port (8088) is not published to the host network, so no user — L1 admin or L2 team human — has any in-band path to this configuration.The worker's qwenpaw app already exposes the complete channel-configuration HTTP API (
/api/config/channels...— the same surface the QwenPaw console Channels page drives through the worker's own client; version-agnostic — verified byte-identical across the official QwenPaw 2.0.1 / 2.2.0 / 2.2.1 releases). This PR adds the Controller proxy in front of it, so channel onboarding becomes a graphical operation from the workbench plugin / dashboard: pick a worker, fill the form, save, verify with health — no SSH, nodocker exec, no hand-editedagent.json.What's included
agentteams-controller/internal/server/worker_channels.go(new) — 9 fixed-path endpoints, fixed forwarding to the worker's qwenpaw channel API under its real/api/config/channels...contract;{channel}charset-validated pre-dial; qrcode/status query restricted to atoken-only whitelist; PUT body forwarded verbatim with empty-body400pre-dial.agentteams-controller/internal/server/http.go— route registration (adjacent block, no other routes touched).agentteams-controller/internal/server/worker_channels_test.go(new) — 21 handler tests, incl. a version-contract table test pinning all 9 forwarded upstream paths.docs/design/worker-channels-api.md— design contract (endpoint table, read-back semantics, role matrix).docs/usage/resource-management.md(+docs/zh-cn/...) — "Worker channel configuration (proxy)" section with the role-permission matrix.Data boundary
agent.jsonand hot-reloads the channel — no worker restart. Upstream400/404/409/422pass through verbatim (the404doubles as the version gate for a qwenpaw build without the router). An empty body is rejected400before the dial — upstream would treat it as an empty config and wipe the saved channel.X-AgentTeams-MinIO-Persistedheader (true/false/skipped): after a200, the Controller reads the MinIO baseline (agents/{name}/.../agent.json) up to 3× (2s apart) and canonical-JSON-compares the channel block against what was saved. The worker'spush_loopremains the single writer of the baseline; the header only observes. This exists because manual local-only edits have left the MinIO baseline stale in production, and a subsequent rebuild'smirror_allthen overwrote the live config with the old baseline — a real credential-loss incident. The header surfaces that gap in-band; the response body stays verbatim.403(the middleware's workerActionUpdatepolicy is same-team for leaders, so the handler is the real enforcement point); cross-team and standalone-worker access hides as404(never403) so the endpoint cannot be used to probe which workers exist or which team owns them.X-Agent-Idthe app resolves the active agent from config — the worker's own agent. No header plumbing needed.503before any worker lookup (existence not probed).Version contract (version-agnostic; verified on 2.0.1 / 2.2.0 / 2.2.1)
/api/config/channels/...— the contract the worker's own client (qwenpaw_worker/api.py) and the integration coverage use. (The first revision forwarded without the/apiprefix, which would have 404'd at runtime; fixed.)404is passed through verbatim (version gate).conflict-checkis a 2.2.x-only route. It is absent from 2.0.x; the 2.2.0 and 2.2.1 channel routers are byte-identical and expose it. Rather than ship a dead endpoint on a 2.0.x pin, the proxy intentionally does not offer it yet; re-adding after a 2.2.x pin is a one-line table entry plus the existing handler shape — an independent small follow-up, not a dependency of this PR.TestChannelsUpstreamPathsMatchWorkerContractpins all 9 forwarded upstream paths against the worker contract (expectations written from the worker client, not from this handler file), so any future worker API move fails the test instead of silently 404-ing.X-AgentTeams-MinIO-Persisted: falsemeans "not yet converged within the bounded read-back window", not "write failed" — the 200 body is authoritative, and the header is advisory when the worker's push-loop interval exceeds the window.Tests
worker_channels_test.go): verbatim forwarding oflist/types/schemas/single; PUT body forwarding + read-backtrue(canonicalization makes the comparison field-order-independent — asserted with a deliberately reordered baseline); read-back converging on the second attempt (simulatedpush_looplag); missing baseline →false; no storage client →skipped; empty body400with zero dials; invalid channel names (../qq,QQ,a.b,x/y) →400with zero dials; cross-team404(no dial); same-team L2 read200and handler-level PUT200; team-leader mutation403+ read200; kube mode503(no dial); unknown worker404; standalone worker hidden from L2 humans / visible to admin; upstream404passthrough verbatim (version gate); upstream500→502;restartmethod+path+body forwarding; version-contract table test pinning all 9 forwarded upstream paths to the worker's real/api/config/channels...contract (expectations written fromqwenpaw_worker/api.py, independent of this handler's source); qrcode/status query whitelist (missing token400, unknown param400, valid token forwarded escaped).go test ./...on themainbaseline: all green except one pre-existing environment failure ininternal/executor(the test sandbox lacks theunzipbinary; CI has it) — untouched by this diff.gofmt/go vetclean.Related
main): the middleware dependency for the L2 write path — with it inmain, the L2 write path (PUT/restart) is functional.404anti-probing, handler-enforced leader read-only);authorizer.gois untouched by this PR, so there is no shared-hunk conflict.docs/design/worker-channels-api.md.Worker 频道配置代理(GET/PUT /api/v1/workers/{name}/channels...)
摘要
Worker 接消息频道(QQ / Matrix / 钉钉 / 飞书 / 微信 …)此前必须宿主机操作:SSH →
docker exec→ 手改agent.json的channels段 → 盯热加载日志。Worker 的 qwenpaw app 端口(8088)未发布到宿主网络,L1 admin 和 L2 团队用户都没有带内配置路径。Worker 的 qwenpaw app 已完整暴露频道配置 HTTP API(
/api/config/channels...——Worker 自身客户端qwenpaw_worker/api.py与集成覆盖使用的真实契约;版本无关——官方 QwenPaw 2.0.1 / 2.2.0 / 2.2.1 三个 release 逐字一致(PyPI hash 核验))。本 PR 在其前加 Controller 代理,频道接入变成图形化操作(工作台插件 / dashboard):选 worker、填表单、保存、health 验证——无 SSH、无docker exec、无手改agent.json。包含内容
agentteams-controller/internal/server/worker_channels.go(新)— 9 个固定路径端点,固定转发到 Worker qwenpaw 频道 API 的真实/api/config/channels...契约;{channel}拨号前字符合入表校验;qrcode/status 查询仅允许token白名单;PUT body 原样转发、空 body 拨号前400拒。agentteams-controller/internal/server/http.go— 路由注册(相邻块,不动其他路由)。agentteams-controller/internal/server/worker_channels_test.go(新)— 21 个 handler 测试,含钉住全部 9 条转发上游路径的版本契约表测试。docs/design/worker-channels-api.md— 设计契约(端点表、读回语义、角色矩阵)。docs/usage/resource-management.md(+ zh-cn)—「Worker channel configuration (proxy)」节,含角色权限矩阵。数据边界
agent.json→ 热加载免重启。上游400/404/409/422原样透传(404兼作无此路由的 qwenpaw 构建的版本门)。空 body 拨号前400拒——上游会把空对象当配置抹掉已存频道。X-AgentTeams-MinIO-Persisted头(true/false/skipped):200后 Controller 读 MinIO 基线(至多 3 次、间隔 2s),canonical-JSON 比对频道块与已存值。push_loop仍是基线唯一写者,该头只观测。动因:生产上手工只写本地曾使 MinIO 基线停旧,后续重建的mirror_all用旧基线覆盖了活配置——真实的凭据丢失事故。该头把这类缺口带内暴露;响应 body 保持原样。403(middleware 对 Leader 的 workerActionUpdate策略是同团队放行,handler 才是真强制点);跨团队与独立 worker 一律404(从不403),端点不可用于探测 worker 存在性或所属团队。X-Agent-Id时 app 从配置解析活动 agent = worker 自身,无需 header 传递。503(不探测存在性)。版本契约(版本无关;已核验 2.0.1 / 2.2.0 / 2.2.1)
/api/config/channels/...——Worker 自身客户端(qwenpaw_worker/api.py)与集成覆盖使用的契约。(首版转发漏了/api前缀,运行时会 404,已修正。)404原样透传(版本门)。TestChannelsUpstreamPathsMatchWorkerContract把全部 9 条转发上游路径钉在 worker 契约上(期望值从 worker 客户端独立写出,不照抄本 handler 源码),worker API 将来移动会先让测试红,而不是运行时静默 404。X-AgentTeams-MinIO-Persisted: false= 「有界读回窗口内未收敛」而非「写失败」——200 body 是权威,worker push-loop 间隔超过窗口时该头为参考值。测试
worker_channels_test.go):list/types/schemas/single原样透传;PUT body 转发 + 读回true(canonical 化使比对与字段顺序无关——用刻意乱序基线断言);读回第 二 次收敛(模拟push_loop滞后);基线缺失 →false;无存储客户端 →skipped;空 body400零拨号;非法频道名(../qq、QQ、a.b、x/y)→400零拨号;跨团队404(不拨号);同团队 L2 读200与 handler 级 PUT200;团队 Leader 写403+ 读200;kube 模式503(不拨号);未知 worker404;独立 worker 对 L2 隐藏 / 对 admin 可见;上游404原样透传(版本门);上游500→502;restart方法+路径+body 转发;版本契约表测试钉住全部 9 条转发上游路径到 worker 真实/api/config/channels...契约(期望值从qwenpaw_worker/api.py独立写出);qrcode/status 查询白名单(缺 token400、未知参数400、合法 token 转义后转发)。go test ./...(21 包)全绿,go build/go vet干净。相关
main):L2 写路径的 middleware 依赖——随其在main,L2 写路径(PUT/restart)已可用。404防探测、handler 强制 Leader 只读);本 PR 不碰authorizer.go,无共享 hunk 冲突。docs/design/worker-channels-api.md。